package client

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/allegro/bigcache"
	"time"

	"gitee.com/mesh_cpn/mesh.cpn.aphros/cache"
	"gitee.com/mesh_cpn/mesh.cpn.aphros/constdef"
	"gitee.com/mesh_cpn/mesh.cpn.aphros/kv"
	clientv3 "go.etcd.io/etcd/client/v3"
	"golang.org/x/sync/singleflight"
)

type ConfigClient struct {
	srv     string
	etcdCli clientv3.Client
	cache   cache.LocalCache
	sfg     singleflight.Group
}

var (
	//configClient        ConfigClient
	etcdClient          clientv3.Client
	ConfigNotFoundError = errors.New("config not found")
)

func (c *ConfigClient) Get(ctx context.Context, key string) (string, error) {
	if len(key) == 0 {
		return "", nil
	}
	return c.getWithCache(ctx, key)
}

func (c *ConfigClient) getWithCache(ctx context.Context, key string) (string, error) {
	cacheValue, cacheErr := c.cache.Get(key, c.srv)
	//没拿到Data
	if cacheValue.Value.Data == "" || cacheErr != nil {
		return c.getAndCache(ctx, cacheValue, cacheErr)
	} else { //拿到
		println("already in local cache")
		data, _, err := c.getValue(cacheValue, cacheErr)
		return data, err
	}
}

func (c *ConfigClient) getValue(cacheValue cache.CachedValue, cacheErr error) (string, int, error) {
	if cacheValue.Value != nil {
		return cacheValue.Data, cacheValue.Version, nil
	}
	return "", -1, cacheErr
}

func (c *ConfigClient) getAndCache(ctx context.Context, cacheValue cache.CachedValue, cacheErr error) (string, error) {
	v, err, _ := c.sfg.Do(cacheValue.Meta.Key, func() (interface{}, error) {
		remoteValue, err := c.getRemoteValue(ctx, cacheValue.Value)
		if err == nil || err == ConfigNotFoundError {
			errSet := c.cache.Set(cacheValue.Meta.Key, remoteValue)
			return remoteValue, errSet
		}
		if cacheValue.Value != nil || cacheErr == ConfigNotFoundError {
			errSet := c.cache.Set(cacheValue.Meta.Key, cacheValue.Value)
			return cacheValue.Data, errSet
		}
		if cacheValue.FirstRequest() {
			errSet := c.cache.Set(cacheValue.Meta.Key, nil)
			return nil, errSet
		}
		return remoteValue, err
	})
	if err != nil {
		return "", err
	}
	return v.(*kv.Value).Data, nil
}

func (c *ConfigClient) getRemoteValue(ctx context.Context, kvValue *kv.Value) (*kv.Value, error) {
	metaBytes, err := c.getFromRemoteDB(ctx, fmt.Sprintf("meta_%v", kvValue.Meta.Key), constdef.RequestTimeout)
	if err != nil {
		return nil, err
	}
	meta := &kv.Meta{}
	err = json.Unmarshal(metaBytes, meta)
	if err != nil {
		return nil, err
	}
	//if meta.Version == kvValue.Meta.Version && meta.LastModifiedAt == kvValue.Meta.LastModifiedAt {
	//	return kvValue, nil //如果kvValue.data==""怎么办
	//}
	valueBytes, err := c.getFromRemoteDB(ctx, fmt.Sprintf("data_%v", kvValue.Meta.Key), constdef.RequestTimeout)
	if err != nil {
		return nil, err
	}
	rt := &kv.Value{
		Data: string(valueBytes),
		Meta: *meta,
	}
	return rt, err
}

func (c *ConfigClient) getFromRemoteDB(ctx context.Context, key string, timeout time.Duration) ([]byte, error) {
	v, err, _ := c.sfg.Do(key, func() (interface{}, error) {
		timerCtx, cancel := context.WithTimeout(ctx, timeout)
		defer cancel()
		resp, err := c.etcdCli.Get(timerCtx, key)
		if err != nil {
			return nil, err
		}
		if len(resp.Kvs) == 0 {
			return nil, fmt.Errorf("nil value")
		}
		return resp.Kvs[0].Value, err
	})
	if err != nil {
		return nil, err
	}
	return v.([]byte), nil
}

//get失败 重试 从etcd失败

func init() {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"localhost:2379"},
		DialTimeout: constdef.EtcdDialTimeout,
	})
	if err != nil {
		panic("etcdClient start error")
	}
	etcdClient = *cli
}

func NewConfigClient(srv string, opts ...OptionHandler) (*ConfigClient, error) {
	op := &Options{
		//EtcdDialTimeout:    5 * time.Second,
		LocalCacheEviction: 5 * time.Second,
		CleanWindow:        5 * time.Second,
	}
	for _, opt := range opts {
		opt(op)
	}

	rt := &ConfigClient{}

	rt.srv = srv //服务名

	rt.etcdCli = etcdClient

	defaultConfig := bigcache.DefaultConfig(op.LocalCacheEviction)
	defaultConfig.CleanWindow = op.CleanWindow
	localCache, err := bigcache.NewBigCache(defaultConfig)
	if err != nil {
		return rt, err
	}
	rt.cache = &cache.MyLocalCache{
		Bcache: localCache,
	}

	return rt, err
}

func Ceshi(client *ConfigClient, s1 string, s2 string) {
	st1 := &kv.Value{
		Data: "111",
	}
	st1.Meta.Key = s1
	st1.Meta.Srv = "123"
	st1.Meta.Version = -1
	st1.Meta.CreateAt = -1
	st1.Meta.Author = ""
	st1.Meta.Editor = -1
	st1.Meta.LastModifiedAt = -1

	st2 := &kv.Value{
		Data: "222",
	}
	st2.Meta.Key = s2
	st2.Meta.Srv = "123"
	st2.Meta.Version = -1
	st2.Meta.CreateAt = -1
	st2.Meta.Author = ""
	st2.Meta.Editor = -1
	st2.Meta.LastModifiedAt = -1

	ctx := context.Background()
	// 设置超时时间
	cxt, cancel := context.WithTimeout(ctx, 100*time.Second)
	defer cancel()

	resp, err := client.etcdCli.Put(cxt, fmt.Sprintf("data_%v", s1), st1.Data)
	if err != nil {
		println("store fail 1")
	}
	println(resp)
	content, err1 := json.Marshal(st1.Meta)
	if err1 != nil {
		println("marshal fail 1")
	}
	resp, err = client.etcdCli.Put(cxt, fmt.Sprintf("meta_%v", s1), string(content))
	if err != nil {
		println("store fail 1")
	}
	println(resp)

	resp, err = client.etcdCli.Put(cxt, fmt.Sprintf("data_%v", s2), st2.Data)
	if err != nil {
		println("store fail 1")
	}
	println(resp)
	content, err1 = json.Marshal(st2.Meta)
	if err1 != nil {
		println("marshal fail 1")
	}
	resp, err = client.etcdCli.Put(cxt, fmt.Sprintf("meta_%v", s2), string(content))
	if err != nil {
		println("store fail 1")
	}
	println(resp)
}
